#include "utils/HardwareInfo.h"
#include "core/Logger.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <memory>
#include <array>
#include <random>

#ifdef __APPLE__
#include <sys/types.h>
#include <sys/sysctl.h>
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#endif

#ifdef __linux__
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <string.h>
#endif

namespace ems {
namespace utils {

std::string HardwareInfo::executeCommand(const std::string& cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe) {
        return "";
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

std::string HardwareInfo::cleanupId(const std::string& id) {
    std::string cleaned = id;
    // Remove whitespace and newlines
    cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), 
                                [](unsigned char c) { return std::isspace(c); }), 
                  cleaned.end());
    // Convert to lowercase
    std::transform(cleaned.begin(), cleaned.end(), cleaned.begin(),
                  [](unsigned char c) { return std::tolower(c); });
    // Remove colons (for MAC addresses)
    cleaned.erase(std::remove(cleaned.begin(), cleaned.end(), ':'), cleaned.end());
    cleaned.erase(std::remove(cleaned.begin(), cleaned.end(), '-'), cleaned.end());
    return cleaned;
}

static std::string takeLast(const std::string& value, std::size_t count) {
    if (value.length() <= count) {
        return value;
    }
    return value.substr(value.length() - count);
}

std::string HardwareInfo::getMacAddress() {
    std::string mac;
    
#ifdef __APPLE__
    // macOS: Use ifconfig to get MAC address
    std::string output = executeCommand("ifconfig | grep ether | head -1");
    size_t pos = output.find("ether");
    if (pos != std::string::npos) {
        pos += 6; // Skip "ether "
        if (pos + 17 <= output.length()) {
            mac = output.substr(pos, 17);
            mac = cleanupId(mac);
        }
    }
#elif defined(__linux__)
    // Linux: Try multiple methods
    // Method 1: ip command
    std::string output = executeCommand("ip link show | grep link/ether | head -1");
    size_t pos = output.find("link/ether");
    if (pos != std::string::npos) {
        pos += 11; // Skip "link/ether "
        if (pos + 17 <= output.length()) {
            mac = output.substr(pos, 17);
            mac = cleanupId(mac);
        }
    }
    
    // Method 2: ifconfig as fallback
    if (mac.empty()) {
        output = executeCommand("ifconfig | grep HWaddr | head -1");
        pos = output.find("HWaddr");
        if (pos != std::string::npos) {
            pos += 7; // Skip "HWaddr "
            if (pos + 17 <= output.length()) {
                mac = output.substr(pos, 17);
                mac = cleanupId(mac);
            }
        }
    }
    
    // Method 3: Read from /sys/class/net/
    if (mac.empty()) {
        output = executeCommand("cat /sys/class/net/*/address 2>/dev/null | grep -v '00:00:00:00:00:00' | head -1");
        if (!output.empty()) {
            mac = cleanupId(output);
        }
    }
#endif
    
    return mac;
}

std::string HardwareInfo::getCpuId() {
    std::string cpuid;
    
#ifdef __APPLE__
    // macOS: Get hardware UUID
    std::string output = executeCommand("system_profiler SPHardwareDataType | grep 'Hardware UUID'");
    size_t pos = output.find("Hardware UUID:");
    if (pos != std::string::npos) {
        pos += 15; // Skip "Hardware UUID: "
        cpuid = output.substr(pos);
        cpuid = cleanupId(cpuid);
    }
#elif defined(__linux__)
    // Linux: Try to get CPU serial number
    // For Raspberry Pi
    std::ifstream cpuinfo("/proc/cpuinfo");
    if (cpuinfo.is_open()) {
        std::string line;
        while (std::getline(cpuinfo, line)) {
            if (line.find("Serial") != std::string::npos) {
                size_t pos = line.find(':');
                if (pos != std::string::npos) {
                    cpuid = line.substr(pos + 1);
                    cpuid = cleanupId(cpuid);
                    break;
                }
            }
        }
        cpuinfo.close();
    }
    
    // For x86/x64 Linux
    if (cpuid.empty()) {
        std::string output = executeCommand("dmidecode -t 4 2>/dev/null | grep ID | head -1");
        size_t pos = output.find("ID:");
        if (pos != std::string::npos) {
            pos += 3;
            cpuid = output.substr(pos);
            cpuid = cleanupId(cpuid);
        }
    }
#endif
    
    return cpuid;
}

std::string HardwareInfo::getMachineId() {
    std::string machine_id;
    
#ifdef __linux__
    // Linux: Read machine-id
    std::ifstream id_file("/etc/machine-id");
    if (id_file.is_open()) {
        std::getline(id_file, machine_id);
        id_file.close();
        machine_id = cleanupId(machine_id);
    }
    
    // Fallback to /var/lib/dbus/machine-id
    if (machine_id.empty()) {
        std::ifstream dbus_id("/var/lib/dbus/machine-id");
        if (dbus_id.is_open()) {
            std::getline(dbus_id, machine_id);
            dbus_id.close();
            machine_id = cleanupId(machine_id);
        }
    }
#elif defined(__APPLE__)
    // macOS: Use IOPlatformSerialNumber
    std::string output = executeCommand("ioreg -l | grep IOPlatformSerialNumber");
    size_t pos = output.find("\"IOPlatformSerialNumber\" = \"");
    if (pos != std::string::npos) {
        pos += 29;
        size_t end = output.find("\"", pos);
        if (end != std::string::npos) {
            machine_id = output.substr(pos, end - pos);
            machine_id = cleanupId(machine_id);
        }
    }
#endif
    
    return machine_id;
}

std::string HardwareInfo::getSystemUUID() {
    std::string uuid;
    
#ifdef __linux__
    // Linux: Try DMI UUID
    std::string output = executeCommand("cat /sys/class/dmi/id/product_uuid 2>/dev/null");
    if (!output.empty()) {
        uuid = cleanupId(output);
    }
    
    // Fallback to dmidecode
    if (uuid.empty()) {
        output = executeCommand("dmidecode -s system-uuid 2>/dev/null");
        if (!output.empty()) {
            uuid = cleanupId(output);
        }
    }
#elif defined(__APPLE__)
    // macOS: Use platform UUID
    std::string output = executeCommand("ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID");
    size_t pos = output.find("\"IOPlatformUUID\" = \"");
    if (pos != std::string::npos) {
        pos += 20;
        size_t end = output.find("\"", pos);
        if (end != std::string::npos) {
            uuid = output.substr(pos, end - pos);
            uuid = cleanupId(uuid);
        }
    }
#endif
    
    return uuid;
}

std::string HardwareInfo::generateFallbackId() {
    // Generate a random ID as last resort
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 15);
    
    std::string id = "ems";
    const char* hex = "0123456789abcdef";
    for (int i = 0; i < 12; ++i) {
        id += hex[dis(gen)];
    }
    
    // Save it to a file for persistence
    std::string id_file = "/tmp/.ems_device_id";
    std::ofstream out(id_file);
    if (out.is_open()) {
        out << id;
        out.close();
    }
    
    LOG_WARN("Generated fallback device ID: " << id);
    return id;
}

std::string HardwareInfo::getDeviceId() {
    std::string device_id;
    
    // Priority 1: Try to read saved device ID
    std::string id_file = "/tmp/.ems_device_id";
    std::ifstream saved_id(id_file);
    if (saved_id.is_open()) {
        std::getline(saved_id, device_id);
        saved_id.close();
        if (!device_id.empty()) {
            LOG_INFO("Using saved device ID: " << device_id);
            return device_id;
        }
    }
    
    // Priority 2: Machine ID (Linux) or Serial Number (macOS)
    device_id = getMachineId();
    if (!device_id.empty()) {
        const bool isLinux =
#if defined(__linux__)
            true;
#else
            false;
#endif
        if (isLinux) {
            const std::string trimmed = takeLast(device_id, 10);
            if (trimmed != device_id) {
                LOG_INFO("Using machine ID as device ID (trimmed to last 10 chars): " << trimmed);
                device_id = trimmed;
            } else {
                LOG_INFO("Using machine ID as device ID: " << device_id);
            }
        } else {
            LOG_INFO("Using machine ID as device ID: " << device_id);
        }
        // Save for future use
        std::ofstream out(id_file);
        if (out.is_open()) {
            out << device_id;
            out.close();
        }
        return device_id;
    }
    
    // Priority 3: CPU ID or Hardware UUID
    device_id = getCpuId();
    if (!device_id.empty()) {
        LOG_INFO("Using CPU/Hardware ID as device ID: " << device_id);
        // Save for future use
        std::ofstream out(id_file);
        if (out.is_open()) {
            out << device_id;
            out.close();
        }
        return device_id;
    }
    
    // Priority 4: System UUID
    device_id = getSystemUUID();
    if (!device_id.empty()) {
        LOG_INFO("Using system UUID as device ID: " << device_id);
        // Save for future use
        std::ofstream out(id_file);
        if (out.is_open()) {
            out << device_id;
            out.close();
        }
        return device_id;
    }
    
    // Last resort: Generate a random ID
    device_id = generateFallbackId();
    return device_id;
}

}  // namespace utils
}  // namespace ems
